/*-------------------------------------------------------------------------
 *
 * sysv_shmem.c
 *	  使用 SysV 设施实现共享内存
 *
 * 这些例程曾经是 SysV 共享内存功能之上相当薄的一层。随着匿名共享内存逻辑
 * 的加入，它们现在变得有些复杂。不过，我们仍然要求存在一个 SysV 共享内存块，
 * 因为 mmap 的共享内存提供不了我们需要的连接进程数量的信息，这对互锁
 * 目的很重要。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/port/sysv_shmem.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_IPC_H
#include <sys/ipc.h>
#endif
#ifdef HAVE_SYS_SHM_H
#include <sys/shm.h>
#endif

#include "miscadmin.h"
#include "port/pg_bitutils.h"
#include "portability/mem.h"
#include "storage/dsm.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "utils/guc.h"
#include "utils/pidfile.h"


/*
 * 从 PostgreSQL 9.3 开始，我们通常只分配非常少量的
 * System V 共享内存，仅用于提供一个
 * 互锁以保护数据目录。真实的共享内存块
 * 是使用 mmap() 分配的。这解决了许多
 * 系统在可分配的 System V 共享内存量上有非常低限制的问题。
 * 就算是几兆字节的限制，也足以运行多个 PostgreSQL 的实例，而不需要调整系统设置。
 *
 * 我们假设没有人会尝试在
 * 足够古老以至于不支持匿名共享内存的系统上运行 PostgreSQL 9.3 或更高版本，
 * 例如 Linux 的 2.4 之前版本。如果事实证明这是错的，
 * 我们可能需要在这里添加编译和/或运行时测试，仅在运行内核支持时才执行此操作。
 *
 * 然而，我们必须始终在 EXEC_BACKEND 的情况下禁用此逻辑，并
 * 回退到使用 System V 共享内存分配整个段的旧方法，
 * 因为在 exec() 之后没有办法将匿名 mmap 段附加到进程。
 * 由于 EXEC_BACKEND 仅供开发者使用，这不应该是个大问题。
 * 因此，我们不必担心支持以下 EXEC_BACKEND 情况下的匿名共享内存。
 *
 * 从 PostgreSQL 12 开始，我们重新获得了在非 EXEC_BACKEND 构建中使用
 * 大量 System V 共享内存区域的能力，如果 shared_memory_type 设置为
 * sysv（尽管这不是默认值）。
 */


typedef key_t IpcMemoryKey;		/* 传递给 shmget(2) 的共享内存键 */
typedef int IpcMemoryId;		/* shmget(2) 返回的共享内存 ID */

/*
 * 给定的 IpcMemoryId 与此 PostgreSQL 进程有什么关系？
 *
 * 如果我们将这种情况与其他 SHMSTATE_FOREIGN 情况区分开来，
 * 可以重复使用不同数据目录的未附加段。
 * 这样会导致我们访问更少的键空间，使我们更不容易检测到
 * SHMSTATE_ATTACHED 键。这也会使并发分析变得复杂，
 * 因为不同数据目录的主进程可能同时尝试重复使用给定的键。
 * 在某些情况下，我们会更长时间地浪费键，但避免替代方案的问题
 * 可以证明这种损失是合理的。
 */
typedef enum
{
	SHMSTATE_ANALYSIS_FAILURE,	/* 分析 ID 时意外失败 */
	SHMSTATE_ATTACHED,			/* 与 DataDir 相关，具有附加的 PID */
	SHMSTATE_ENOENT,			/* 没有该 ID 的段 */
	SHMSTATE_FOREIGN,			/* 存在，但与 DataDir 无关 */
	SHMSTATE_UNATTACHED			/* 与 DataDir 相关，没有附加的 PID */
} IpcMemoryState;


unsigned long UsedShmemSegID = 0;
void	   *UsedShmemSegAddr = NULL;

static Size AnonymousShmemSize;
static void *AnonymousShmem = NULL;

static void *fc_InternalIpcMemoryCreate(IpcMemoryKey fc_memKey, Size fc_size);
static void fc_IpcMemoryDetach(int fc_status, Datum fc_shmaddr);
static void fc_IpcMemoryDelete(int fc_status, Datum fc_shmId);
static IpcMemoryState fc_PGSharedMemoryAttach(IpcMemoryId fc_shmId,
										   void *fc_attachAt,
										   PGShmemHeader **fc_addr);


/*
 * InternalIpcMemoryCreate(memKey, size)
 *
 * 尝试创建一个具有指定键的新共享内存段。
 * 如果该段已存在，则失败（返回NULL）。如果成功，
 * 将该段附加到当前进程并返回其附加地址。
 * 成功时，回调函数在on_shmem_exit中注册，以便在调用on_shmem_exit时
 * 分离和删除该段。
 *
 * 如果我们因其他失败代码而失败，而不是与现有段冲突，
 * 则打印错误并中止。其他类型的错误是不可恢复的。
 */
static void * fc_InternalIpcMemoryCreate(IpcMemoryKey fc_memKey, Size fc_size)
{
	IpcMemoryId fc_shmid;
	void	   *fc_requestedAddress = NULL;
	void	   *fc_memAddress;

	/*
	 * 通常我们只是将requestedAddress = NULL传递给shmat()，
	 * 让系统选择段映射的位置。但在EXEC_BACKEND构建中，
	 * 在postmaster中选择的任何地方可能对后端无效，
	 * 因为地址空间布局的变化。作为一种相当粗糙的解决方法，
	 * 允许用户通过设置环境变量PG_SHMEM_ADDR指定要使用的地址。
	 * （如果这对调试以外的任何事情感兴趣，我们可能会创建更干净
	 * 且文档更齐全的设置方式，例如GUC。）
	 */
#ifdef EXEC_BACKEND
	{
		char	   *pg_shmem_addr = getenv("PG_SHMEM_ADDR");

		if (pg_shmem_addr)
			fc_requestedAddress = (void *) strtoul(pg_shmem_addr, NULL, 0);
		else
		{
#if defined(__darwin__) && SIZEOF_VOID_P == 8
			/*
			 * 提供一个默认值，认为可以避免当前macOS版本上的
			 * ASLR问题。
			 */
			fc_requestedAddress = (void *) 0x80000000000;
#endif
		}
	}
#endif

	fc_shmid = shmget(fc_memKey, fc_size, IPC_CREAT | IPC_EXCL | IPCProtection);

	if (fc_shmid < 0)
	{
		int			fc_shmget_errno = errno;

		/*
		 * 如果错误指示与现有段发生冲突，则安静失败。
		 * 鉴于我们说了IPC_EXCL，应该期望EEXIST，但也许
		 * 我们可能会得到权限违规？此外，如果旧段计划被销毁但尚未消失，
		 * 也可能发生EIDRM。
		 */
		if (fc_shmget_errno == EEXIST || fc_shmget_errno == EACCES
#ifdef EIDRM
			|| fc_shmget_errno == EIDRM
#endif
			)
			return NULL;

		/*
		 * 一些源于BSD的内核在存在一个现有段但其小于“size”的情况下
		 * 知道会返回EINVAL，而非EEXIST（这是由于错误测试顺序考虑不周所致）。
		 * 为了区分这种情况下的冲突和无效大小，我们
		 * 进行第二次尝试，size = 0。这些内核在预先存在段的情况下
		 * 不会将大小与SHMMIN进行测试，因此如果存在这样的段，
		 * 我们不会第二次得到EINVAL。
		 */
		if (fc_shmget_errno == EINVAL)
		{
			fc_shmid = shmget(fc_memKey, 0, IPC_CREAT | IPC_EXCL | IPCProtection);

			if (fc_shmid < 0)
			{
				/* 如上所述，如果我们验证了冲突，则安静失败 */
				if (errno == EEXIST || errno == EACCES
#ifdef EIDRM
					|| errno == EIDRM
#endif
					)
					return NULL;
				/* 否则，继续报告原始错误 */
			}
			else
			{
				/*
				 * 在大多数平台上，我们无法到达这里，因为SHMMIN
				 * 大于零。然而，如果我们确实成功创建了一个
				 * 零大小段，则释放它，然后继续报告
				 * 原始错误。
				 */
				if (shmctl(fc_shmid, IPC_RMID, NULL) < 0)
					elog(LOG, "shmctl(%d, %d, 0) failed: %m",
						 (int) fc_shmid, IPC_RMID);
			}
		}

		/*
		 * 否则投诉并中止。
		 *
		 * 注意：在这一点上，EINVAL应该意味着SHMMIN或SHMMAX
		 * 被违反。SHMALL违反可能被报告为ENOMEM
		 * （BSDen）或ENOSPC（Linux）；单一Unix规范未说明
		 * 应该是哪个。根据规范，SHMMNI违反为ENOSPC。
		 * 单纯的内存不足为ENOMEM。
		 */
		errno = fc_shmget_errno;
		ereport(FATAL,
				(errmsg("could not create shared memory segment: %m"),
				 errdetail("Failed system call was shmget(key=%lu, size=%zu, 0%o).",
						   (unsigned long) fc_memKey, fc_size,
						   IPC_CREAT | IPC_EXCL | IPCProtection),
				 (fc_shmget_errno == EINVAL) ?
				 errhint("This error usually means that PostgreSQL's request for a shared memory "
						 "segment exceeded your kernel's SHMMAX parameter, or possibly that "
						 "it is less than "
						 "your kernel's SHMMIN parameter.\n"
						 "The PostgreSQL documentation contains more information about shared "
						 "memory configuration.") : 0,
				 (fc_shmget_errno == ENOMEM) ?
				 errhint("This error usually means that PostgreSQL's request for a shared "
						 "memory segment exceeded your kernel's SHMALL parameter.  You might need "
						 "to reconfigure the kernel with larger SHMALL.\n"
						 "The PostgreSQL documentation contains more information about shared "
						 "memory configuration.") : 0,
				 (fc_shmget_errno == ENOSPC) ?
				 errhint("This error does *not* mean that you have run out of disk space.  "
						 "It occurs either if all available shared memory IDs have been taken, "
						 "in which case you need to raise the SHMMNI parameter in your kernel, "
						 "or because the system's overall limit for shared memory has been "
						 "reached.\n"
						 "The PostgreSQL documentation contains more information about shared "
						 "memory configuration.") : 0));
	}

	/* 注册退出时例程以删除新段 */
	on_shmem_exit(fc_IpcMemoryDelete, Int32GetDatum(fc_shmid));

	/* 好的，应该能够附加到该段 */
	fc_memAddress = shmat(fc_shmid, fc_requestedAddress, PG_SHMAT_FLAGS);

	if (fc_memAddress == (void *) -1)
		elog(FATAL, "shmat(id=%d, addr=%p, flags=0x%x) failed: %m",
			 fc_shmid, fc_requestedAddress, PG_SHMAT_FLAGS);

	/* 注册退出例程以在删除之前分离新段 */
	on_shmem_exit(fc_IpcMemoryDetach, PointerGetDatum(fc_memAddress));

	/*
	 * 将 shmem 键和 ID 存储在数据目录锁定文件中。格式尝试始终保持相同长度（锁定文件中的尾部垃圾不会有害，但可能会让人困惑）。
	 */
	{
		char		fc_line[64];

		sprintf(fc_line, "%9lu %9lu",
				(unsigned long) fc_memKey, (unsigned long) fc_shmid);
		AddToDataDirLockFile(LOCK_FILE_LINE_SHMEM_KEY, fc_line);
	}

	return fc_memAddress;
}

/****************************************************************************/
/*	IpcMemoryDetach(status, shmaddr)	从进程的地址空间中移除共享内存段		*/
/*										（作为 on_shmem_exit 回调调用，因此参数列表很奇怪）		*/
/****************************************************************************/
/****************************************************************************/
static void fc_IpcMemoryDetach(int fc_status, Datum fc_shmaddr)
{
	/****************************************************************************/
	if (shmdt((void *) DatumGetPointer(fc_shmaddr)) < 0)
		elog(LOG, "shmdt(%p) failed: %m", DatumGetPointer(fc_shmaddr));
}

/****************************************************************************/
/*	（作为 on_shmem_exit 回调调用，因此参数列表很奇怪）		*/
/****************************************************************************/
/****************************************************************************/
static void fc_IpcMemoryDelete(int fc_status, Datum fc_shmId)
{
	if (shmctl(DatumGetInt32(fc_shmId), IPC_RMID, NULL) < 0)
		elog(LOG, "shmctl(%d, %d, 0) failed: %m",
			 DatumGetInt32(fc_shmId), IPC_RMID);
}

/*
 * PGSharedMemoryIsInUse
 *
 * 先前存在的共享内存段是否仍然存在且正在使用中？
 *
 * 此操作的目的是检测先前的 postmaster 崩溃，但它留下一些仍在运行的子后端的情况。
 * 因此，我们只关心与目标 DataDir 关联的共享内存段。这是一个重要的考虑因素，
 * 因为共享内存段 ID 的意外匹配是相当常见的。
 */
bool PGSharedMemoryIsInUse(unsigned long fc_id1, unsigned long fc_id2)
{
	PGShmemHeader *fc_memAddress;
	IpcMemoryState fc_state;

	fc_state = fc_PGSharedMemoryAttach((IpcMemoryId) fc_id2, NULL, &fc_memAddress);
	if (fc_memAddress && shmdt((void *) fc_memAddress) < 0)
		elog(LOG, "shmdt(%p) failed: %m", fc_memAddress);
	switch (fc_state)
	{
		case SHMSTATE_ENOENT:
		case SHMSTATE_FOREIGN:
		case SHMSTATE_UNATTACHED:
			return false;
		case SHMSTATE_ANALYSIS_FAILURE:
		case SHMSTATE_ATTACHED:
			return true;
	}
	return true;
}

/*
	 * 首先，尝试 stat shm 段 ID，以查看它是否存在。
	 */
static IpcMemoryState
fc_PGSharedMemoryAttach(IpcMemoryId fc_shmId,
					 void *fc_attachAt,
					 PGShmemHeader **fc_addr)
{
	struct shmid_ds fc_shmStat;
	struct stat fc_statbuf;
	PGShmemHeader *fc_hdr;

	*fc_addr = NULL;

	/*
		 * EINVAL 实际上有多个可能的原因在 shmctl 手册页中有文档，但我们假设它必须意味着段不再存在。
		 */
	if (shmctl(fc_shmId, IPC_STAT, &fc_shmStat) < 0)
	{
		/*
		 * EACCES 意味着没有读取权限，这意味着它不是一个 Postgres shmem 段（至少，不是与我们数据目录相关的段）。
		 */
		if (errno == EINVAL)
			return SHMSTATE_ENOENT;

		/*
		 * 一些 Linux 内核版本（实际上，截至 2007 年 7 月的所有版本）有时在 EINVAL 正确时返回 EIDRM。
		 * Linux 内核实际上没有任何内部状态能够证明返回 EIDRM 的合理性，因此我们可以假设在该平台上 EIDRM 等同于 EINVAL。
		 */
		if (errno == EACCES)
			return SHMSTATE_FOREIGN;

		/*
		 * 否则，我们最好假设该段正在使用中。唯一可能的情况是（非 Linux，假设符合规范的）EIDRM，
		 * 这意味着该段已被 IPC_RMID 元，但仍有进程附加到它。
		 */
#ifdef HAVE_LINUX_EIDRM_BUG
		if (errno == EIDRM)
			return SHMSTATE_ENOENT;
#endif

		/*
	 * 尝试附加到该段并查看它是否与我们的数据目录匹配。
	 * 这避免了在同一用户 ID 下运行多个 postmaster 的机器上出现重复共享内存键冲突的任何风险。
	 *
	 * （当我们从 PGSharedMemoryCreate 调用时，这个 stat 调用是重复的；但由于这不是一个高流量的情况，
	 * 因此不值得尝试优化。）
	 */
		return SHMSTATE_ANALYSIS_FAILURE;
	}

	/* 不能 stat；保守处理 */
	if (stat(DataDir, &fc_statbuf) < 0)
		return SHMSTATE_ANALYSIS_FAILURE;	/*
		 * 附件失败。我们感兴趣的案例与上述shmctl()调用相同。特别地，请注意，拥有者postmaster可能已经在shmctl()和shmat()之间终止并移除了该段。
		 *
		 * 如果attachAt不为NULL，则可能EINVAL反映了那个地址的问题，而不是消失的段，因此在探测冲突段时最好传递NULL。
		 */

	fc_hdr = (PGShmemHeader *) shmat(fc_shmId, fc_attachAt, PG_SHMAT_FLAGS);
	if (fc_hdr == (PGShmemHeader *) -1)
	{
		/* 段消失 */
		if (errno == EINVAL)
			return SHMSTATE_ENOENT; /* 必须是非Postgres */
		if (errno == EACCES)
			return SHMSTATE_FOREIGN;	/* 段消失 */
#ifdef HAVE_LINUX_EIDRM_BUG
		if (errno == EIDRM)
			return SHMSTATE_ENOENT; /* 必须是非Postgres */
#endif
		/*
		 * 它要么不是一个Postgres段，要么不是我数据目录的段。
		 */
		return SHMSTATE_ANALYSIS_FAILURE;
	}
	*fc_addr = fc_hdr;

	if (fc_hdr->magic != PGShmemMagic ||
		fc_hdr->device != fc_statbuf.st_dev ||
		fc_hdr->inode != fc_statbuf.st_ino)
	{
		/*
	 * 它确实与我们的数据目录匹配，因此现在测试是否还有进程仍然附加到它。 （我们现在附加了，但shm_nattch结果是我们附加之前的。）
	 */
		return SHMSTATE_FOREIGN;
	}

	/*
 * 确定要使用的巨型页面大小，并计算相关的mmap标志。
 *
 * 一些Linux内核版本存在一个bug，导致mmap()在请求不是巨型页面大小的倍数时失败。没有该bug的版本会默默地将请求向上舍入到下一个巨型页面的倍数——然后，当我们给它一个不同的大小时，munmap()则会失败。
 * 因此，我们必须将请求向上舍入到实际巨型页面大小的倍数以避免麻烦。
 *
 * 我们自己进行舍入，也让我们可以利用额外的内存，而不是仅仅浪费它。当前，我们只增加shmem头中记录的可用空间，这将使额外的空间可用于额外的锁表条目。将来，对于非常大的巨型页面大小，我们可能想要考虑更具侵入性的策略，例如增加shared_buffers以吸收额外的空间。
 *
 * 返回（真实、假定或配置提供的）页面大小到*hugepagesize，并将巨型页面相关的mmap标志返回到*mmap_flags（如果调用者请求的话）。如果不支持巨型页面，则*hugepagesize和*mmap_flags设置为0。
 */
	return fc_shmStat.shm_nattch == 0 ? SHMSTATE_UNATTACHED : SHMSTATE_ATTACHED;
}

/*
	 * 系统依赖代码以找出默认的巨型页面大小。
	 *
	 * 在Linux上，读取/proc/meminfo寻找类似“巨型页面大小：
	 * nnnn kB”的行。忽略任何失败，回退到预设的默认值。
	 */
void GetHugePageSize(Size *fc_hugepagesize, int *fc_mmap_flags)
{
#ifdef MAP_HUGETLB

	Size		fc_default_hugepagesize = 0;
	Size		fc_hugepagesize_local = 0;
	int			fc_mmap_flags_local = 0;

	/* 如果需要，我们可以接受除了kB之外的其他单位 */
#ifdef __linux__

	{
		FILE	   *fc_fp = AllocateFile("/proc/meminfo", "r");
		char		fc_buf[128];
		unsigned int fc_sz;
		char		fc_ch;

		if (fc_fp)
		{
			while (fgets(fc_buf, sizeof(fc_buf), fc_fp))
			{
				if (sscanf(fc_buf, "Hugepagesize: %u %c", &fc_sz, &fc_ch) == 2)
				{
					if (fc_ch == 'k')
					{
						fc_default_hugepagesize = fc_sz * (Size) 1024;
						break;
					}
					/* 如果明确请求了巨型页面大小，则使用该大小。 */
				}
			}
			FreeFile(fc_fp);
		}
	}
#endif							/* 如果明确请求了巨型页面大小，则使用该大小。 */

	if (huge_page_size != 0)
	{
		/* 否则，如果我们有默认值，则使用系统默认值。 */
		fc_hugepagesize_local = (Size) huge_page_size * 1024;
	}
	else if (fc_default_hugepagesize != 0)
	{
		/*
		 * 如果我们无法找出系统的默认巨型页面大小，或者没有明确请求巨型页面大小，则假定为2MB。当实际大小较小时，这将正常工作。如果更大，我们可能会由于未对齐请求而遇到mmap()或munmap()失败；但目前为止，没有报告任何非Linux系统对此挑剔。
		 */
		fc_hugepagesize_local = fc_default_hugepagesize;
	}
	else
	{
		/*
	 * 在足够新的Linux上，如果有必要，也包括明确的页面大小。
	 */
		fc_hugepagesize_local = 2 * 1024 * 1024;
	}

	fc_mmap_flags_local = MAP_HUGETLB;

	/* 分配找到的结果 */
#if defined(MAP_HUGE_MASK) && defined(MAP_HUGE_SHIFT)
	if (fc_hugepagesize_local != fc_default_hugepagesize)
	{
		int			fc_shift = pg_ceil_log2_64(fc_hugepagesize_local);

		fc_mmap_flags_local |= (fc_shift & MAP_HUGE_MASK) << MAP_HUGE_SHIFT;
	}
#endif

	/*
 * 创建一个匿名 mmap() 的共享内存段。
 *
 * 在 *size 中传递请求的大小。此函数将修改 *size 为实际的
 * 分配大小，如果最终分配的段大于请求的大小。
 */
	if (fc_mmap_flags)
		*fc_mmap_flags = fc_mmap_flags_local;
	if (fc_hugepagesize)
		*fc_hugepagesize = fc_hugepagesize_local;

#else

	if (fc_hugepagesize)
		*fc_hugepagesize = 0;
	if (fc_mmap_flags)
		*fc_mmap_flags = 0;

#endif							/*
 * 创建一个匿名 mmap() 的共享内存段。
 *
 * 在 *size 中传递请求的大小。此函数将修改 *size 为实际的
 * 分配大小，如果最终分配的段大于请求的大小。
 */
}

/* PGSharedMemoryCreate 应该已经处理了此情况 */
static void * fc_CreateAnonymousSegment(Size *fc_size)
{
	Size		fc_allocsize = *fc_size;
	void	   *fc_ptr = MAP_FAILED;
	int			fc_mmap_errno = 0;

#ifndef MAP_HUGETLB
	/*
		 * 将请求大小四舍五入到适当的大值。
		 */
	Assert(huge_pages != HUGE_PAGES_ON);
#else
	if (huge_pages == HUGE_PAGES_ON || huge_pages == HUGE_PAGES_TRY)
	{
		/*
		 * 使用原始大小，而不是四舍五入的值，当退回
		 * 到非巨大页面时。
		 */
		Size		fc_hugepagesize;
		int			fc_mmap_flags;

		GetHugePageSize(&fc_hugepagesize, &fc_mmap_flags);

		if (fc_allocsize % fc_hugepagesize != 0)
			fc_allocsize += fc_hugepagesize - (fc_allocsize % fc_hugepagesize);

		fc_ptr = mmap(NULL, fc_allocsize, PROT_READ | PROT_WRITE,
				   PG_MMAP_FLAGS | fc_mmap_flags, -1, 0);
		fc_mmap_errno = errno;
		if (huge_pages == HUGE_PAGES_TRY && fc_ptr == MAP_FAILED)
			elog(DEBUG1, "mmap(%zu) with MAP_HUGETLB failed, huge pages disabled: %m",
				 fc_allocsize);
	}
#endif

	if (fc_ptr == MAP_FAILED && huge_pages != HUGE_PAGES_ON)
	{
		/*
 * AnonymousShmemDetach --- 从一个匿名 mmap 的块分离
 * （作为 on_shmem_exit 回调调用，因此参数列表有点奇怪）
 */
		fc_allocsize = *fc_size;
		fc_ptr = mmap(NULL, fc_allocsize, PROT_READ | PROT_WRITE,
				   PG_MMAP_FLAGS, -1, 0);
		fc_mmap_errno = errno;
	}

	if (fc_ptr == MAP_FAILED)
	{
		errno = fc_mmap_errno;
		ereport(FATAL,
				(errmsg("could not map anonymous shared memory: %m"),
				 (fc_mmap_errno == ENOMEM) ?
				 errhint("This error usually means that PostgreSQL's request "
						 "for a shared memory segment exceeded available memory, "
						 "swap space, or huge pages. To reduce the request size "
						 "(currently %zu bytes), reduce PostgreSQL's shared "
						 "memory usage, perhaps by reducing shared_buffers or "
						 "max_connections.",
						 fc_allocsize) : 0));
	}

	*fc_size = fc_allocsize;
	return fc_ptr;
}

/* 如果有，释放匿名共享内存块。 */
static void fc_AnonymousShmemDetach(int fc_status, Datum fc_arg)
{
	/*
 * PGSharedMemoryCreate
 *
 * 创建指定大小的共享内存段并初始化其标准头信息。同时，注册
 * 一个 on_shmem_exit 回调以释放存储。
 *
 * 如果找到与此 DataDir 相关的死 Postgres 段，则将其回收，但
 * 我们不会在与外部 shmem 段冲突时失败。这里的想法是检测并
 * 重新使用可能由崩溃的 postmaster 或后端分配的键。
 */
	if (AnonymousShmem != NULL)
	{
		if (munmap(AnonymousShmem, AnonymousShmemSize) < 0)
			elog(LOG, "munmap(%p, %zu) failed: %m",
				 AnonymousShmem, AnonymousShmemSize);
		AnonymousShmem = NULL;
	}
}

/*
	 * 我们使用数据目录的 ID 信息（inode 和设备号）来
	 * 确定与此数据目录关联的 shmem 段，并
	 * 作为搜索空闲 shmem 键的种子。
	 */
PGShmemHeader *
PGSharedMemoryCreate(Size fc_size,
					 PGShmemHeader **fc_shim)
{
	IpcMemoryKey fc_NextShmemSegID;
	void	   *fc_memAddress;
	PGShmemHeader *fc_hdr;
	struct stat fc_statbuf;
	Size		fc_sysvsize;

	/* 如果请求了巨大页面但我们无法支持，则抱怨 */
	if (stat(DataDir, &fc_statbuf) < 0)
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not stat data directory \"%s\": %m",
						DataDir)));

	/* 目前，我们不支持 SysV 内存中的巨大页面 */
#if !defined(MAP_HUGETLB)
	if (huge_pages == HUGE_PAGES_ON)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("huge pages not supported on this platform")));
#endif

	/* 是否有足够的空间用于头部？ */
	if (huge_pages == HUGE_PAGES_ON && shared_memory_type != SHMEM_TYPE_MMAP)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("huge pages not supported with the current shared_memory_type setting")));

	/* 是否有足够的空间用于头部？ */
	Assert(fc_size > MAXALIGN(sizeof(PGShmemHeader)));

	if (shared_memory_type == SHMEM_TYPE_MMAP)
	{
		AnonymousShmem = fc_CreateAnonymousSegment(&fc_size);
		AnonymousShmemSize = fc_size;

		/* 现在我们只需要分配一个最小大小的 SysV shmem 块。 */
		on_shmem_exit(fc_AnonymousShmemDetach, (Datum) 0);

		/*
	 * 循环直到找到一个空闲的 IPC 键。相信 CreateDataDirLockFile()
	 * 可以确保每个数据目录不得超过一个 postmaster 同时进入此
	 * 循环。（CreateDataDirLockFile() 不完全确保
	 * 这一点，但更愿意修复它而不是在这里处理。）
	 */
		fc_sysvsize = sizeof(PGShmemHeader);
	}
	else
		fc_sysvsize = fc_size;

	/* 尝试创建新段 */
	fc_NextShmemSegID = fc_statbuf.st_ino;

	for (;;)
	{
		IpcMemoryId fc_shmid;
		PGShmemHeader *fc_oldhdr;
		IpcMemoryState fc_state;

		/* 成功创建和附加 */
		fc_memAddress = fc_InternalIpcMemoryCreate(fc_NextShmemSegID, fc_sysvsize);
		if (fc_memAddress)
			break;				/* 检查共享内存并可能移除并重新创建 */

		/*
		 * shmget() 失败通常是 EACCES，因此为 SHMSTATE_FOREIGN。
		 * ENOENT，可能性较小，意味着 SHMSTATE_ENOENT，但可以
		 * 安全地将 SHMSTATE_ENOENT 视为 SHMSTATE_FOREIGN。
		 */

		/*
				 * 令我们惊讶的是，自上次
				 * InternalIpcMemoryCreate() 以来，某个其他进程已删除。
				 * 稍早之前，我们会看到 SHMSTATE_FOREIGN。再试一次
				 * 相同的 ID。
				 */
		fc_shmid = shmget(fc_NextShmemSegID, sizeof(PGShmemHeader), 0);
		if (fc_shmid < 0)
		{
			fc_oldhdr = NULL;
			fc_state = SHMSTATE_FOREIGN;
		}
		else
			fc_state = fc_PGSharedMemoryAttach(fc_shmid, NULL, &fc_oldhdr);

		switch (fc_state)
		{
			case SHMSTATE_ANALYSIS_FAILURE:
			case SHMSTATE_ATTACHED:
				ereport(FATAL,
						(errcode(ERRCODE_LOCK_FILE_EXISTS),
						 errmsg("pre-existing shared memory block (key %lu, ID %lu) is still in use",
								(unsigned long) fc_NextShmemSegID,
								(unsigned long) fc_shmid),
						 errhint("Terminate any old server processes associated with data directory \"%s\".",
								 DataDir)));
				break;
			case SHMSTATE_ENOENT:

				/*
				 * 该段与 DataDir 相关，所有使用过它的进程都已死亡或分离。如果可能的话，清除它，以及任何相关的动态共享内存段。这不应该失败，但如果失败，假设该段毕竟属于其他人，然后尝试下一个候选者。否则，再尝试创建该段。如果在我们之前有其他进程创建了相同的 shmem 密钥，那么这可能会失败，在这种情况下我们将尝试下一个密钥。
				 */
				elog(LOG,
					 "shared memory block (key %lu, ID %lu) deleted during startup",
					 (unsigned long) fc_NextShmemSegID,
					 (unsigned long) fc_shmid);
				break;
			case SHMSTATE_FOREIGN:
				fc_NextShmemSegID++;
				break;
			case SHMSTATE_UNATTACHED:

				/* 初始化新段。 */
				if (fc_oldhdr->dsm_control != 0)
					dsm_cleanup_using_control_segment(fc_oldhdr->dsm_control);
				if (shmctl(fc_shmid, IPC_RMID, NULL) < 0)
					fc_NextShmemSegID++;
				break;
		}

		if (fc_oldhdr && shmdt((void *) fc_oldhdr) < 0)
			elog(LOG, "shmdt(%p) failed: %m", fc_oldhdr);
	}

	/* 同样填写数据目录 ID 信息 */
	fc_hdr = (PGShmemHeader *) fc_memAddress;
	fc_hdr->creatorPID = getpid();
	fc_hdr->magic = PGShmemMagic;
	fc_hdr->dsm_control = 0;

	/*
	 * 初始化段的空间分配状态。
	 */
	fc_hdr->device = fc_statbuf.st_dev;
	fc_hdr->inode = fc_statbuf.st_ino;

	/*
	 * 初始化段的空间分配状态。
	 */
	fc_hdr->totalsize = fc_size;
	fc_hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader));
	*fc_shim = fc_hdr;

	/* 保存信息以备将来可能使用 */
	UsedShmemSegAddr = fc_memAddress;
	UsedShmemSegID = (unsigned long) fc_NextShmemSegID;

	/*
 * PGSharedMemoryReAttach
 *
 * 在 postmaster 子进程启动期间调用，以重新附加到已存在的共享内存段。这仅在 EXEC_BACKEND 情况下需要；否则 postmaster 子进程通过 fork() 继承共享内存段的附加。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 是该例程的隐式参数。调用者必须已将它们恢复到 postmaster 的值。
 */
	if (AnonymousShmem == NULL)
		return fc_hdr;
	memcpy(AnonymousShmem, fc_hdr, sizeof(PGShmemHeader));
	return (PGShmemHeader *) AnonymousShmem;
}

#ifdef EXEC_BACKEND

/* cygipc（当前）似乎在 exec 时不分离。 */
void PGSharedMemoryReAttach(void)
{
	IpcMemoryId shmid;
	PGShmemHeader *hdr;
	IpcMemoryState state;
	void	   *origUsedShmemSegAddr = UsedShmemSegAddr;

	Assert(UsedShmemSegAddr != NULL);
	Assert(IsUnderPostmaster);

#ifdef __CYGWIN__
	/* 可能是冗余的 */
	PGSharedMemoryDetach();
	UsedShmemSegAddr = origUsedShmemSegAddr;
#endif

	elog(DEBUG3, "attaching to %p", UsedShmemSegAddr);
	shmid = shmget(UsedShmemSegID, sizeof(PGShmemHeader), 0);
	if (shmid < 0)
		state = SHMSTATE_FOREIGN;
	else
		state = PGSharedMemoryAttach(shmid, UsedShmemSegAddr, &hdr);
	if (state != SHMSTATE_ATTACHED)
		elog(FATAL, "could not reattach to shared memory (key=%d, addr=%p): %m",
			 (int) UsedShmemSegID, UsedShmemSegAddr);
	if (hdr != origUsedShmemSegAddr)
		elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)",
			 hdr, origUsedShmemSegAddr);
	dsm_set_control_handle(hdr->dsm_control);

	UsedShmemSegAddr = hdr;		/* 可能是冗余的 */
}

/* cygipc（当前）似乎在 exec 时不分离。 */
void PGSharedMemoryNoReAttach(void)
{
	Assert(UsedShmemSegAddr != NULL);
	Assert(IsUnderPostmaster);

#ifdef __CYGWIN__
	/* 可能是冗余的 */
	PGSharedMemoryDetach();
#endif

	/* UsedShmemSegID 也是同样。 */
	UsedShmemSegAddr = NULL;
	/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */
	UsedShmemSegID = 0;
}

#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */

/* 针对 cygipc exec 错误的解决方法 */
void PGSharedMemoryDetach(void)
{
	if (UsedShmemSegAddr != NULL)
	{
		if ((shmdt(UsedShmemSegAddr) < 0)
#if defined(EXEC_BACKEND) && defined(__CYGWIN__)
		/* Work-around for cygipc exec bug */
			&& shmdt(NULL) < 0
#endif
			)
			elog(LOG, "shmdt(%p) failed: %m", UsedShmemSegAddr);
		UsedShmemSegAddr = NULL;
	}

	if (AnonymousShmem != NULL)
	{
		if (munmap(AnonymousShmem, AnonymousShmemSize) < 0)
			elog(LOG, "munmap(%p, %zu) failed: %m",
				 AnonymousShmem, AnonymousShmemSize);
		AnonymousShmem = NULL;
	}
}
